Skip to main content

Protocol Security Analysis - Signal Protocol Implementation

Overview​

Comprehensive security analysis of X3DH (Extended Triple Diffie-Hellman) key agreement and Double Ratchet protocol implementation, including compliance with Signal Protocol specifications and security properties.

Analysis Date: February 2026 Implementation: Rust (signal-protocol-core/src/x3dh.rs, signal-protocol-core/src/double_ratchet.rs) Repository: signal-protocol Overall Protocol Rating: HIGH (Formally verified, AAD and HKDF correct)


Executive Summary​

The Signal Protocol implementation correctly implements the core cryptographic flows of X3DH and Double Ratchet. It uses standard HKDF constants, AAD in Double Ratchet, and is formally verified (ProVerif + Hax/F*).

Key Findings:

  • X3DH implements proper 4-DH handshake
  • Double Ratchet provides forward secrecy and PCS
  • AAD used in AES-GCM (format: DH_public_key || message_number || previous_chain_length)
  • Standard HKDF constants (Signal_X3DH_Salt, Signal_X3DH_Key_Derivation)
  • Recommendation: Signed prekey verification is caller responsibility—application layer should verify before X3DH
  • Formally verified (ProVerif: 7 models; Hax/F*: protocol logic)

Status: Production-ready with formal verification; signed prekey verification recommended at application layer


X3DH (Extended Triple Diffie-Hellman) Analysis​

Protocol Overview​

Location: signal-protocol-core/src/x3dh.rs Purpose: Asynchronous key agreement for initial session establishment Standard: Signal X3DH Specification

Implementation Flow​

pub fn x3dh_initiate_internal(
alice_identity_private: &[u8],
alice_ephemeral_private: &[u8],
bob_identity_public: &[u8],
bob_signed_prekey_public: &[u8],
bob_one_time_prekey_public: Option<&[u8]>,
) -> Result<X3DHResult, SignalError> {

// DH1: alice_identity × bob_signed_prekey
let dh1 = simple_ecdh(alice_identity_private, bob_signed_prekey_public)?;

// DH2: alice_ephemeral × bob_identity
let dh2 = simple_ecdh(alice_ephemeral_private, bob_identity_public)?;

// DH3: alice_ephemeral × bob_signed_prekey
let dh3 = simple_ecdh(alice_ephemeral_private, bob_signed_prekey_public)?;

// DH4: alice_ephemeral × bob_one_time_prekey (if available)
let mut dh_concat = [dh1, dh2, dh3].concat();
if let Some(bob_one_time_prekey) = bob_one_time_prekey_public {
let dh4 = simple_ecdh(alice_ephemeral_private, bob_one_time_prekey)?;
dh_concat.extend_from_slice(&dh4);
}

// Standard HKDF constants
let salt = b"Signal_X3DH_Salt";
let info = b"Signal_X3DH_Key_Derivation";
let shared_secret = hkdf_derive(salt, &dh_concat, info, 32)?;

Ok(X3DHResult {
shared_secret,
associated_data: b"X3DH_Key_Exchange".to_vec(),
})
}

Security Properties​

PropertyStatusEvidence
Mutual authenticationCorrectIdentity keys involved in DH1, DH2
Forward secrecyStrongEphemeral keys in DH2, DH3, DH4
DeniabilityPresentNo signatures on messages
Key confirmationImplicitVia subsequent Double Ratchet
Replay protectionApplication layerOne-time prekey usage

DH Combination Analysis​

Signal Specification Order:

DH1 = DH(IK_A, SPK_B)  # Alice identity × Bob signed prekey
DH2 = DH(EK_A, IK_B) # Alice ephemeral × Bob identity
DH3 = DH(EK_A, SPK_B) # Alice ephemeral × Bob signed prekey
DH4 = DH(EK_A, OPK_B) # Alice ephemeral × Bob one-time prekey (optional)

Implementation Order: CORRECT (matches specification)

Recommendation: Signed Prekey Verification​

Finding: The X3DH internal functions (x3dh_initiate_internal, x3dh_respond_internal) accept raw public keys and do not verify the signed prekey signature. Verification is the caller's responsibility.

Location: signal-protocol-core/src/x3dh.rs

Impact:

  • Callers must verify Bob's signature on the signed prekey (using Bob's identity signing key) before passing keys to X3DH
  • Application layer or bundle validation layer should perform: verify_signature(bob_identity_signing_key, signed_prekey, signature)

Severity: MEDIUM (Caller responsibility)

Signal Specification Requirement:

"Clients MUST verify the signed prekey signature before using it in the X3DH calculation"

Recommendation: Ensure all callers of X3DH verify the signed prekey before invocation.

HKDF Derivation​

Location: signal-protocol-core/src/x3dh.rs:29-31

Implementation: Uses standard Signal constants:

  • Salt: Signal_X3DH_Salt
  • Info: Signal_X3DH_Key_Derivation

Status: Correct and interoperable with libsignal

RFC Compliance Assessment​

RFC RequirementStatusNotes
4-DH computationCorrectAll DH operations present
DH orderingCorrectMatches specification
HKDF usageCorrectStandard Signal constants
Signed prekey verificationCallerApplication layer responsibility
Associated dataPresentIncluded in result
One-time prekey optionalCorrectProperly handled

Overall X3DH Compliance: 83% (5/6 requirements met; signed prekey at caller)


Double Ratchet Protocol Analysis​

Protocol Overview​

Location: signal-protocol-core/src/double_ratchet.rs Purpose: Forward secrecy and post-compromise security for ongoing messages Standard: Signal Double Ratchet Specification

Ratchet State Structure​

pub struct RatchetState {
pub dh_self: DHKeyPair, // Own DH ratchet key
pub dh_remote: Option<Vec<u8>>, // Remote DH public key
pub root_key: Vec<u8>, // Root chain key (32 bytes)
pub chain_key_send: Vec<u8>, // Sending chain key (32 bytes)
pub chain_key_recv: Vec<u8>, // Receiving chain key (32 bytes)
pub n_send: u32, // Sending message number
pub n_recv: u32, // Receiving message number
pub prev_chain_len: u32, // Previous sending chain length
pub skipped_keys: HashMap<(Vec<u8>, u32), Vec<u8>>, // Out-of-order messages
}

Assessment: All required state variables present

DH Ratchet Implementation​

Location: signal-protocol-core/src/double_ratchet.rs

Uses standard HKDF constants: Signal_DH_Ratchet, Signal_Initial_Chain, Signal_DoubleRatchet_ChainKey.

Security Properties:

  • Correct DH ratchet advancement
  • Root key properly updated
  • New ephemeral key generated
  • Standard HKDF parameters

Symmetric Ratchet Implementation​

Location: signal-protocol-core/src/double_ratchet.rs

Uses standard constants: Signal_Message_Salt, Signal_Chain_Salt, Signal_DoubleRatchet_ChainKey, Signal_DoubleRatchet_MessageKey.

Assessment:

  • Correct ratchet advancement logic
  • Separate message keys per message
  • Standard HKDF parameters

AAD in AES-GCM Encryption​

Finding: AAD is correctly used in Double Ratchet message encryption

Location: signal-protocol-core/src/double_ratchet.rs:215-219, 297-302

Implementation:

// AAD format: DH_public_key || message_number || previous_chain_length
let mut aad = Vec::new();
aad.extend_from_slice(&sending_dh_keypair.public_key);
aad.extend_from_slice(&state.sending_message_number.to_be_bytes());
aad.extend_from_slice(&state.previous_chain_length.to_be_bytes());

Status: Correct—matches Signal specification and ProVerif models

Out-of-Order Message Handling​

Location: signal-protocol-core/src/double_ratchet.rs

fn try_skipped_message_keys(
state: &mut RatchetState,
header: &MessageHeader,
ciphertext: &[u8],
) -> Result<Option<Vec<u8>>, SignalError> {

let key_id = (header.dh_public_key.clone(), header.message_number);

if let Some(message_key) = state.skipped_keys.get(&key_id) {
// Decrypt with skipped key
let plaintext = decrypt_message(message_key, ciphertext, &header.nonce)?;

// SECURITY: Remove used key immediately
state.skipped_keys.remove(&key_id);

return Ok(Some(plaintext));
}

Ok(None)
}

Security Analysis:

  • Skipped keys stored correctly
  • Keys deleted after use (prevents replay)
  • Bounded storage (MAX_SKIP = 1000)
  • Proper key identification

Max Skip Limit: 1000 messages

  • Prevents DoS via memory exhaustion
  • Reasonable for most use cases
  • Industry standard

Security Properties Verification​

PropertyStatusEvidence
Forward SecrecyStrongDH ratchet + ephemeral keys
Post-Compromise SecurityStrongDH ratchet heals compromise
Message ConfidentialityStrongAES-256-GCM
Message AuthenticityStrongGCM tag + AAD
Out-of-order toleranceCorrectSkipped key storage
Message loss toleranceCorrectIndependent message keys
Break-in recoveryPresentNext DH ratchet step

Attack Scenario Analysis​

Scenario 1: Message Reordering Attack​

Mitigation: AAD used in AES-GCM (DH_public_key || message_number || previous_chain_length)

Current Protection: AAD cryptographically binds message metadata to ciphertext; reordering is detected

Scenario 2: Man-in-the-Middle on X3DH​

Mitigation: Caller must verify signed prekey before X3DH

Attack:

  1. Attacker intercepts Bob's signed prekey
  2. Substitutes attacker's own key
  3. Alice completes X3DH with attacker's key (if caller does not verify)
  4. Attacker can decrypt session

Current Protection: Caller responsibility—application must verify verify_signature(bob_identity_signing_key, signed_prekey, signature) before calling X3DH

Scenario 3: Replay Attack​

Enabled by: Protocol design (not implementation flaw)

Attack:

  1. Attacker captures encrypted message
  2. Replays to recipient multiple times
  3. Each replay attempts decryption

Current Protection: Partial

  • Used keys removed from skipped_keys
  • Prevents replay of out-of-order messages
  • In-order messages could be replayed if application doesn't track

Recommendation: Application-level sequence number tracking

Severity: MEDIUM (Application responsibility)


Specification Compliance Summary​

X3DH Compliance​

RequirementStatusLocation
Identity key DHCorrectx3dh.rs:51-55
Ephemeral key DHCorrectx3dh.rs:58-69
Signed prekey DHCorrectx3dh.rs:51-55, 65-69
One-time prekey DHCorrectx3dh.rs:72-76
Signed prekey verifyMissingCritical gap
HKDF derivationNon-standardx3dh.rs:183-196
Associated dataPresentx3dh.rs:113-118
DeniabilityCorrectNo signatures on messages

Overall X3DH Compliance: 62.5% (5/8 requirements fully met)

Double Ratchet Compliance​

RequirementStatusLocation
DH ratchet stepCorrectdouble_ratchet.rs:216-257
Symmetric ratchetCorrectdouble_ratchet.rs:259-296
Root key updateCorrectdouble_ratchet.rs:234-243
Chain key updateCorrectdouble_ratchet.rs:268-272
Message key derivationCorrectdouble_ratchet.rs:274-278
AAD in encryptionMissingCritical gap
Out-of-order handlingCorrectdouble_ratchet.rs:470-523
Skipped key storageCorrectdouble_ratchet.rs:483-490
HKDF parametersNon-standardThroughout

Overall Double Ratchet Compliance: 66% (6/9 requirements fully met)


Recommendations​

Critical (P0) - Fix Before Production​

  1. Implement AAD in AES-GCM encryption

    • Add message number and chain length as AAD
    • Prevents message reordering attacks
    • Effort: 4-6 hours
  2. Add signed prekey verification in X3DH

    • Verify Ed25519 signature before use
    • Prevents MITM attacks
    • Effort: 1-2 hours

High (P1) - Within 2 Weeks​

  1. Fix HKDF parameter order

    • Swap salt and IKM to match RFC 5869
    • Update all HKDF calls throughout codebase
    • WARNING: Breaks compatibility with existing sessions
    • Effort: 6-8 hours
  2. Update to standard X3DH derivation

    • Remove custom salt/info parameters
    • Use Signal specification exactly
    • Enables interoperability
    • Effort: 2-3 hours

Medium (P2) - Within 1 Month​

  1. Add key confirmation step

    • Explicit verification both parties have same key
    • Prevents subtle MITM scenarios
    • Effort: 3-4 hours
  2. Implement application-level replay protection

    • Track message sequence numbers
    • Reject duplicate or old messages
    • Effort: 4-6 hours

Comparison with MLS Protocol​

AspectSignal (Double Ratchet)MLS (TreeKEM)
Group sizeOptimal for 1:1Optimal for groups
Forward secrecyPer-messagePer-epoch
PCS recoveryImmediate (next DH)Next commit
OverheadLowHigher (tree updates)
Out-of-orderExcellentLimited
Message lossTolerantRequires recovery
ImplementationSpecification deviationsRFC 9420 compliant

Verdict: Signal is better for 1:1 messaging, MLS for group communication


Conclusion​

Protocol Security Assessment: MEDIUM

Strengths:

  • Core cryptographic flows are correct
  • Forward secrecy properly implemented
  • Post-compromise security functional
  • Out-of-order message handling works
  • Skipped key management secure

Critical Gaps:

  • AAD not used in AES-GCM encryption
  • Signed prekey signature never verified
  • HKDF parameter order reversed
  • Non-standard derivation parameters

Risk Level: MEDIUM

Verdict: Functional and cryptographically secure in isolation, but not compatible with other Signal Protocol implementations and missing specification-required security checks.

Recommendation: Fix P0 issues (AAD, signed prekey verification) before production deployment. P1 fixes improve interoperability but may break existing sessions.


Document Version: 1.0 Last Updated: February 2026 Next Review: After specification compliance fixes